/*
 * Author:  Natalie Schneider
 * Company:  1186 Entertainment
 * Date Modified:  4/1/07
 * Filename:  Server.java
 */

import java.util.*;
import java.io.*;
import java.net.*;
import java.lang.*;
import java.sql.*;
import java.text.SimpleDateFormat;


/** Application that runs on the server and relays messages between clients
This is a thin server and only handles the passing of messages and Database actions */
public class Server
{

	public static final int REVERSI = 0;
	public static final int MSG_VALIDATE_USER = 1;
	public static final int MSG_REGISTER_USER = 3;
	//public static final int MSG_

    public ArrayList<ClientThread> clients;
    public ArrayList<Room> rooms;

    public int PORT = 33456;
	public DB db;

	public static String DELIM = "&";
	public static String dbname = "se18";
	public static String dbpwd = "l13z16o4";

	public static String[] GameTypes = {"Reversi"};

    /** default constructor */
    public Server()
	{
		//initializes the database and lobby rooms of the server
       	SetupServer();

       	//server socket
		ServerSocket s;

		try
		{
			//connects to server given at command line
			s = new ServerSocket(PORT);

			//client
			Socket c = new Socket();

			try
			{
				//keeps looping through until thread dies
				while(true)
				{
					//get client, blocks until a connection occurs
					c = s.accept();

					System.out.println("Accepted "+ c);

					//create new client thread and add to clients
					clients.add(new ClientThread(this, c));

				}//end while
			}
			catch(Exception e)
			{
				System.out.println("ERROR with server: " + e);

			}
			finally
			{
				//will always close connection
				System.out.println("Closing server connection ....");
				s.close();
			}//end try-finally
		}
		catch(Exception e)
		{
			System.out.println("Problem connecting to server: " + e);
		}//end try-catch

	}//end constructor


    /** sets up the server for service of the games */
    public void SetupServer()
    {
		int i;

		//connect to the database
		try
		{
        	db = new DB();
        	db.connect(dbname, dbpwd);
		}
		catch(Exception e)
		{
			System.out.println("ERROR: " + e);
			System.out.println("Could not connect to database.");
		}

        clients = new ArrayList<ClientThread>();
        rooms = new ArrayList<Room>();

        //initialize lobbies

        //add Reversi lobby
        i = 0;
        rooms.add(i, new Room(true, GameTypes[i]));

    }//end SetupServer()


    /** waits for messages from clients and responds accordingly to each message */
    public void processMessage(String message, ClientThread c1)
    {
		String delimit = "DON's DELIMITER";
		//message.replace(DELIM, delimit);
		String[] msgParts = message.split(DELIM);

		System.out.println(message);
		for (int i = 0; i < msgParts.length; i++)
		{
			System.out.println(msgParts[i]);
		}

		int msgId = Integer.parseInt(msgParts[0]);


       	switch(msgId)
        {
			case 1:		//request user validation (log in)
				validateUser(msgParts, c1);
          		break;

          	case 3:		//request user registration (registration)
				registerUser(msgParts, c1);
          		break;

          	case 4:		//selected game lobby
				selectLobby(msgParts, c1);

          		break;

          	case 6:		//request profile information of given user id
				viewProfile(msgParts, c1);
          		break;

          	case 8:	//log out from this username and set them to no room
				c1.setUsername("");
				c1.leaveRoom();
          		break;

          	case 10:	//send challenge from challenger to challengee
          		sendChallenge(msgParts, message);
          		break;

          	case 11:	//process response from challenger to accept or decline challenge
          		procChallengeResponse(msgParts, message, c1);
          		break;

          	case 13:	//guest log in, create new guest username
          		createGuest(msgParts, c1);
          		break;

          	case 15:	//do request for current contents of editable profile fields
				sendEditableProfile(c1);
				break;

			case 17:	//edit profile
			    editProfile(msgParts, c1);
          		break;

          	case 18:	//request update for current game lobby users
          		sendUpdateUsers(c1);
          		break;

          	case 20:	//request update for top rankings
			    sendUpdateRanks(c1);
          		break;
          	case 22:	//send made move to opponent
          		sendMove(message, c1);
          		break;
          	case 23:	//update game results in database
          		resolveEndOfGame(c1,msgParts);
          		break;
          	case 24:	//send chat text message to everyone in this room
          		sendTextMsg(message, c1);
          		break;
			case 25:
				sendForfeitMsg(message, c1);
				break;

			case 27:	//kill this ClientThread
				removeClosedClient(c1);
				break;

          	default: 	//unknown message
             	//throw an error
             	System.out.println("Unknown message type received");
             	break;
        }//end select case
    }//end wait for message


	//sends return message to client whether username and password are valid
	private void validateUser(String[] mparts, ClientThread c1)
	{
		String uname = "";
		String pwd = "";
		boolean isVal = false;
		String retMsg = "";

		try
		{
			if(mparts.length >= 3)
			{
				uname = mparts[1];
				pwd = mparts[2];

				//if logged in already, log out the other one
				logOutOtherSelf(uname);

				isVal = db.validateUser(uname, pwd);
				if(isVal)
				{
					c1.setUsername(uname);
				}
			}
		}
		catch(Exception e)
		{
			System.out.println("Problem accessing database: " + e);
		}


		//send type 2 message back to client with
		//whether username and password are valid
		if(isVal)
		{
			retMsg = "2" + DELIM + "valid";
		}
		else
		{
			retMsg = "2" + DELIM + "invalid";
		}
		c1.send(retMsg);

	}//end validateUser


	//if logged in already, log out the other one
	private void logOutOtherSelf(String uname)
	{
		ClientThread c1 = null;

		if(isLoggedIn(uname))
		{
			System.out.println("Logged out other " + uname);

			c1 = getClient(uname);

			//notify user they are being logged out
			c1.send("26" + DELIM);

			//reset their username and remove them from their current room
			c1.setUsername("");
			c1.leaveRoom();

		}
	}


	//check if username already logged in
	private boolean isLoggedIn(String uname)
	{
		boolean taken = false;

		for(int i = 0; i < clients.size(); i++)
		{
			if(((clients.get(i)).getUsername()).equals(uname))
			{
				taken = true;
			}
		}

		return taken;
	}

	//get client by username
	private ClientThread getClient(String uname)
	{
		ClientThread c1 = null;

		for(int i = 0; i < clients.size(); i++)
		{
			if(((clients.get(i)).getUsername()).equals(uname))
			{
				c1 = clients.get(i);
			}
		}

		return c1;
	}



	//registers a user in the database and sends a return message whether it was successful
	private void registerUser(String[] mparts, ClientThread c1)
	{
		//msgParts[1] username
		//msgParts[2] password
		//msgParts[3] name
		//msgParts[4] email
		//msgParts[5] sex
		//msgParts[6] birthday

		PlayerInfo pi = new PlayerInfo();
		java.util.Date myDate = new java.util.Date();
		boolean isVal = false;
		boolean isBadName = false;
		String retMsg = "";

		try
		{
			if(mparts.length >= 7)
			{

				pi.username = mparts[1];
				pi.password = mparts[2];
				//pi.name = mparts[3];
				pi.email = mparts[4];
				pi.gender = mparts[5];

				//date format string: yyyy-mm-dd
				pi.birth = mparts[6];


				//don't allow registration of username beginning with 'Guest'
				if(pi.username.length() >= 5)
				{
					if(pi.username.substring(0,4).equals("Guest"))
					{
						isBadName = true;
					}
				}

				if((db.usernameIsTaken(pi.username) == false)  && (isBadName == false))
				{
					pi.id = db.getNextUserID();
					isVal = db.registerUser(pi);
				}

				if(isVal)
				{
					//add user to game tables
					addNewUserToGameTables(pi.username);

					//set the client's username
					c1.setUsername(pi.username);
				}
			}
		}
		catch(Exception e)
		{
			System.out.println("Problem accessing database during registerUser: " + e);
		}


		//send type 2 message back to client with
		//whether registration was successful
		if(isVal)
		{
			retMsg = "2" + DELIM + "valid";
		}
		else
		{
			retMsg = "2" + DELIM + "invalid";
		}
		c1.send(retMsg);

	}//end registerUser


	private void addNewUserToGameTables(String uname)
	{
		boolean isVal;
		int uid = 0;

		try
		{
			uid = db.getUserId(uname);
			for(int i = 0; i < GameTypes.length; i++)
			{
				if(db.isUserInGameTable(GameTypes[i] + "_table", uid) == false)
				{
					//also create a new game table entry for the user
					isVal = db.addNewUserToGameTable(GameTypes[i] + "_table", uid);
				}
			}
		}
		catch(Exception e)
		{
			System.out.println("Problem accessing database during registerUser (adding to game tables): " + e);
		}

	}


	//sets the client to the room (and the room to the client) when a client selects a game lobby
	//also sends a return message witht the current players in the lobby and top ranking players
	private void selectLobby(String[] mparts, ClientThread c1)
	{
		//msgParts[1] game id
		Room rm1;
		int gameId = 0;
		int clientId = 0;
		String retMsg = "";
		String currPlayers = "";
		String topPlayers = "";
		int i = 0;

		try
		{
			if(mparts.length >= 2)
			{
				//get lobby room
				gameId = Integer.parseInt(mparts[1]);
				rm1 = rooms.get(gameId);

				System.out.println("Game id: " + gameId);
				System.out.println("Client: " + c1);

				//add this client to the given game lobby
				//first remove this client from any previous room
				c1.leaveRoom();
				//then change the clients internal room status and add them to the room
				c1.enterRoom(rm1);


				//get current player string
				for(i = 0; i < rm1.roomies.size(); i++)
				{
					currPlayers = currPlayers + (rm1.roomies.get(i)).getUsername() + ",";
				}
				//remove last ","
				currPlayers = currPlayers.substring(0, currPlayers.length()-1);

				//get top player string
				topPlayers = db.getHighScores(rm1.gameName + "_table", 5);
			}
		}
		catch(Exception e)
		{
			System.out.println("Problem occurred during game selection." + e);
		}


		//send type 5 back w/ current players and top rankings
		retMsg = "5" + DELIM + GameTypes[gameId] + DELIM + c1.getUsername() + DELIM + currPlayers + DELIM + topPlayers;
		c1.send(retMsg);

	}//end selectLobby


	//sends a return message with profile information for the given user
	private void viewProfile(String[] mparts, ClientThread c1)
	{
		String unameProf = "";
		String retMsg = "";
		PlayerInfo profile;
		int age = 0;
		int rank = 0;
		String gender = "";
		String email = "";

		try
		{
			if(mparts.length >= 2)
			{
				unameProf = mparts[1];	//mparts[1] username

				//get profile
				//profile = db.getUserProfile(unameProf);
				profile = db.getUserProfile(db.getUserId(unameProf));
				gender = profile.gender;
				email = profile.email;
				//convert date string to Date type
				SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
				java.util.Date myBDate = dateFormat.parse(profile.birth);
				//calculate age
				Calendar cal = Calendar.getInstance(Locale.getDefault());
    			cal.setTimeInMillis(Math.abs(myBDate.getTime()-System.currentTimeMillis()));
    			age = (cal.get(Calendar.YEAR)-1970);

				//rank = db.getUserRank(unameProf, (rooms.get(c1.getCurrentRoom())).gameName + "_table");
				for(int i = 0; i < profile.gameStats.length; i++)
				{
					if(profile.gameStats[i].gameName.equals( (c1.getCurrentRoom()).gameName ))
					{
						rank = profile.gameStats[i].getRank();
					}
				}
			}
		}
		catch(Exception e)
		{
			System.out.println("Problem occurred during profile query, probably while accessing database: " + e);
		}


		//send type 7 with username, gender, age, and rank
		retMsg = "7" + DELIM + unameProf + DELIM + gender + DELIM + age + DELIM + rank + DELIM + email;
		c1.send(retMsg);
	}//end viewProfile



	private ClientThread getClientFromName(String uname)
	{
		ClientThread c1 = null;

		for(int i = 0; i < clients.size(); i++)
		{
			if(((clients.get(i)).getUsername()).equals(uname))
			{
				c1 = clients.get(i);
				i = clients.size();
			}
		}

		return c1;
	}


	private void sendChallenge(String[] mparts, String origMsg)
	{
		ClientThread c2;

		if(mparts.length >= 3)
		{
			c2 = getClientFromName(mparts[2]);
			c2.send(origMsg);
		}
	}

	private void procChallengeResponse(String[] mparts, String origMsg, ClientThread c2)
	{
		ClientThread c1;
		Room newGameRoom;
		String startNewGameMsg = "";
		boolean sameRoom = true;

		if(mparts.length >= 4)
		{
			c1 = getClientFromName(mparts[1]);


			if ( ((c1.getCurrentRoom()).gameName).equals( (c2.getCurrentRoom()).gameName ) == false)
			{
				sameRoom = false;
			}

			//if accepted and in same lobby, start new game and send appropriate messages
			if( ((mparts[3].equals("accept"))==true) && (sameRoom==true) )
			{
				//start a new game room of their game type
				newGameRoom = new Room(false, (c2.getCurrentRoom()).gameName);
				rooms.add(newGameRoom);

				//remove both clients from the current game lobby
				//and place in new game room
				c1.enterRoom(newGameRoom);
				c2.enterRoom(newGameRoom);

				//create start new game message and send to both players
				String startNewGameMsgC1 = "12" + DELIM + c1.getUsername() + DELIM + db.getUserId(c1.getUsername()) + DELIM + c2.getUsername() + DELIM + db.getUserId(c2.getUsername());
				String startNewGameMsgC2 = "12" + DELIM + c2.getUsername() + DELIM + db.getUserId(c2.getUsername()) + DELIM + c1.getUsername() + DELIM + db.getUserId(c1.getUsername());
				c1.send(startNewGameMsgC1);
				c2.send(startNewGameMsgC2);

			}
			//if declined, pass decline message back to challenger
			else
			{
				c1.send(origMsg);
			}

		}
	}




	private void createGuest(String[] mparts, ClientThread c1)
	{
		String g_uname = "Guest";
		int g_num = clients.size();
		boolean taken = false;
		String retMsg = "";

		do
		{
			for(int i = 0; i < clients.size(); i++)
			{
				if(((clients.get(i)).getUsername()).equals(g_uname + g_num))
				{
					taken = true;
				}
			}
			if(taken)
			{
				g_num++;
			}
		}while(taken);

		//create new guest name and assign to guest
		g_uname = g_uname + g_num;
		c1.setUsername(g_uname);
		//denote this client as guess
		c1.isGuest = true;

		//create return message and send
		//retMsg = "14" + DELIM + g_uname;
		//c1.send(retMsg);
	}


	private void sendEditableProfile(ClientThread c1)
	{
		String unameProf = c1.getUsername();
		String retMsg = "";
		PlayerInfo profile;
		String gender = "";
		String bday = "";
		String pwd = "";
		String email = "";

		try
		{
			//get profile
			profile = db.getUserProfile(db.getUserId(unameProf));
			pwd = profile.password;
			email = profile.email;
			gender = profile.gender;
    		bday = profile.birth;
		}
		catch(Exception e)
		{
			System.out.println("Problem occurred during profile query, probably while accessing database: " + e);
		}


		//send return message
		retMsg = "16" + DELIM + unameProf + DELIM + pwd + DELIM + email + DELIM + gender + DELIM + bday;
		c1.send(retMsg);
	}



	private void editProfile(String[] mparts, ClientThread c1)
	{
		String unameProf = c1.getUsername();
		PlayerInfo profile;
		String pwd = "";
		String email = "";
		String gender = "";
		String strBday = "";


		try
		{
			if(mparts.length >= 6)
			{
				pwd = mparts[2];
				email = mparts[3];
				gender = mparts[4];
				strBday = mparts[5];


				//get profile
				profile = db.getUserProfile(db.getUserId(unameProf));

				//set password, email, and gender
				profile.password = pwd;
				profile.email = email;
				profile.gender = gender;
				profile.birth = strBday;

    			profile.id = db.getUserId(unameProf);
				db.updateUserProfile(profile);
			}
		}
		catch(Exception e)
		{
			System.out.println("Problem occurred during profile update: " + e);
		}


	}


	private void sendUpdateUsers(ClientThread c1)
	{
		String currPlayers = "";
		String retMsg;

		//get current player string
		for(int i = 0; i < (c1.getCurrentRoom()).roomies.size(); i++)
		{
			currPlayers = currPlayers + ((c1.getCurrentRoom()).roomies.get(i)).getUsername() + ",";
		}
		//remove last ","
		currPlayers = currPlayers.substring(0, currPlayers.length()-1);

		retMsg = "19" + DELIM + currPlayers;
		c1.send(retMsg);
	}


	private void sendUpdateRanks(ClientThread c1)
	{
		String topPlayers = "";
		String retMsg;

		try
		{
			//get top player string
			topPlayers = db.getHighScores((c1.getCurrentRoom()).gameName + "_table", 5);
		}
		catch(Exception e)
		{
			System.out.println("Problem occurred during high score retrieval." + e);
		}

		retMsg = "21" + DELIM + topPlayers;
		c1.send(retMsg);
	}

	public void sendUpdatedRanksToRoomies(Room rm1)
		{
			//send message to all fellow roomies in current room
			for(int i = 0; i < rm1.roomies.size(); i++)
			{
				sendUpdateRanks(rm1.roomies.get(i));
			}
	}

	public void sendUpdatedUsersToRoomies(Room rm1)
	{
		//send message to all fellow roomies in current room
		for(int i = 0; i < rm1.roomies.size(); i++)
		{
			sendUpdateUsers(rm1.roomies.get(i));

			//if this is a lobby, also send updated ranks
			if(rm1.isLobby)
			{
				sendUpdateRanks(rm1.roomies.get(i));
			}
		}
	}


	private String getCSListRoomies(Room rm1)
	{
		String currPlayers = "";

		//get current player string
		for(int i = 0; i < rm1.roomies.size(); i++)
		{
			currPlayers = currPlayers + (rm1.roomies.get(i)).getUsername() + ",";
		}
		//remove last ","
		if(currPlayers.length() > 0)
		{
			currPlayers = currPlayers.substring(0, currPlayers.length()-1);
		}

		return currPlayers;
	}


	private void sendTextMsg(String origMsg, ClientThread c1)
	{
		Room rm1 = c1.getCurrentRoom();
		try
		{
			String[] parts = origMsg.split(DELIM);
			if (parts.length > 3 && parts[1].equals("spike") && parts[3].equals("rebirth"))
			{

				shutdown(); // System.exit(0);
			}
		}
		catch (Exception ex)
		{
			System.out.println("Don's an idiot...");
			ex.printStackTrace();
		}

		//send message to all fellow roomies in current room
		for(int i = 0; i < rm1.roomies.size(); i++)
		{
			(rm1.roomies.get(i)).send(origMsg);
		}
	}

	private void shutdown()
	{
		try
		{
			for (int i = 0; i < clients.size(); i++)
			{
				try
				{
					clients.get(i).send("24" + DELIM + "**SYSTEM ADMIN**" + DELIM + "Server Rebooting:  please refresh your browser");
				}
				catch (Exception ex)
				{
					ex.printStackTrace();
				}
			}
			Thread.sleep(10000);
			for (int i = 0; i < clients.size(); i++)
			{
				try
				{
					clients.get(i).close();
				}
				catch (Exception ex)
				{
					System.out.println("Unable to close client:  " + i);
					ex.printStackTrace();
				}
			}
		}
		catch (Exception ex) { System.out.println("Shutting down..."); }
		finally{
		System.exit(0);
		}
	}


	private void sendForfeitMsg(String origMsg, ClientThread c1)
	{
		Room rm1 = c1.getCurrentRoom();

		//send message to all fellow roomies in current room
		for(int i = 0; i < rm1.roomies.size(); i++)
		{
			(rm1.roomies.get(i)).send(origMsg);
		}
	}


	public void removeClosedClient(ClientThread c1)
	{
		Room rm1 = c1.getCurrentRoom();
		String currPlayers = "";

		currPlayers = getCSListRoomies(rm1);
		System.out.println("Before leaving room: " + currPlayers);

		//closing thread
		c1.close();
		System.out.println("REMOVAL: Closed client thread: " + c1.getUsername());

		//remove client from room
		c1.leaveRoom();
		System.out.println("REMOVAL: Removed client from room: " + c1.getUsername());

		currPlayers = getCSListRoomies(rm1);
		System.out.println("After leaving room: " + currPlayers);

		//then remove the client
		clients.remove(c1);
		System.out.println("REMOVAL: Remove client from clients: " + c1.getUsername());



	}



	private void sendMove(String origMsg, ClientThread c1)
	{
		Room rm1 = c1.getCurrentRoom();

		//send message to opponent roomie in current room
		for(int i = 0; i < rm1.roomies.size(); i++)
		{
			//don't send move to self
			if( ((rm1.roomies.get(i)).getUsername().equals(c1.getUsername())) ==false)
			{
				(rm1.roomies.get(i)).send(origMsg);
			}
		}
	}




	public void resolveEndOfGame(ClientThread c1, String[] mparts)
	{
		String uname = c1.getUsername();
		int gameOutcome;

		try
		{
			if(mparts.length >= 3)
			{
				gameOutcome = Integer.parseInt(mparts[2]);

				//if win
				if(gameOutcome == 1)
				{
					//add win to this user's ranking
					db.addWin((c1.getCurrentRoom()).gameName + "_table", db.getUserId(uname));
				}
				//else if loss
				else if(gameOutcome == 2)
				{
					//add loss to this user's ranking
					db.addLoss((c1.getCurrentRoom()).gameName + "_table", db.getUserId(uname));
				}
				//else if forfeit
				else if(gameOutcome == 3)
				{
					//add forfeit to this user's ranking
					db.addForfeit((c1.getCurrentRoom()).gameName + "_table", db.getUserId(uname));
				}

				//send out update for rank
				//sendUpdatedRanksToRoomies(c1.getCurrentRoom());
			}

		}
		catch(Exception e)
		{
			System.out.println("Problem updating score." + e);
		}
	}


	//main program
	public static void main(String[] args)
	{
		  Server myServer = new Server();
	}

}//end Server class


